#ifndef _MYSQLSERVER_HPP
#define _MYSQLSERVER_HPP

#include <jdbc/cppconn/statement.h>
#include <jdbc/cppconn/resultset.h>
#include <jdbc/cppconn/exception.h>
#include "jdbc/mysql_driver.h"

namespace builtin {
	class MysqlServer {
	public:
		Object* connect(std::vector<Object*> argLists) {
			if (argLists.size() != 5) {
				return new Error("MysqlServer->connect function must have 5 parameters");
			}
			std::string ip = argLists[0]->examine();
			if (argLists[1]->Type() != INTEGER_OBJECT) {
				return new Error("MysqlServer->connect function's second parameter must be integer");
			}
			Integer* integer = dynamic_cast<Integer*>(argLists[1]);
			int port = integer->_value;
			std::string userName = argLists[2]->examine();
			std::string password = argLists[3]->examine();
			std::string schema = argLists[4]->examine();

			try {
				/* Create a connection */
				_driver = get_driver_instance();
				std::string hostName = "tcp://" + ip + ":" + std::to_string(port);
				sql::ConnectOptionsMap connection_properties;
				connection_properties["hostName"] = hostName;
				connection_properties["userName"] = userName;
				connection_properties["password"] = password;
				connection_properties["schema"] = schema;
				connection_properties["characterSetResults"] = "utf8mb4";
				_con = _driver->connect(connection_properties);
				/* Connect to the MySQL test database */
			}
			catch (sql::SQLException& e) {
				cout << "# ERR: SQLException in " << __FILE__;
				cout << "(" << __FUNCTION__ << ") on line "
					<< __LINE__ << endl;
				cout << "# ERR: " << e.what();
				cout << " (MySQL error code: " << e.getErrorCode();
				cout << ", SQLState: " << e.getSQLState() << " )" << endl;
				return new Boolean(false);
			}
			return new Boolean(true);
		}

		Object* query(std::vector<Object*> argLists) {
			if (argLists.size() != 1) {
				return new Error("MysqlServer->query function must have one parameter");
			}
			try {
				sql::Statement* stmt = _con->createStatement();
				_res = stmt->executeQuery(argLists[0]->examine());
				delete stmt;
				return new Boolean(true);
			}
			catch (sql::SQLException& e) {
				cout << "# ERR: SQLException in " << __FILE__;
				cout << "(" << __FUNCTION__ << ") on line "
					<< __LINE__ << endl;
				cout << "# ERR: " << e.what();
				cout << " (MySQL error code: " << e.getErrorCode();
				cout << ", SQLState: " << e.getSQLState() << " )" << endl;
				return new Boolean(false);
			}
		}

		Object* execute(std::vector<Object*> argLists) {
			if (argLists.size() != 1) {
				return new Error("MysqlServer->query function must have one parameter");
			}
			try {
				sql::Statement* stmt = _con->createStatement();
				auto res = stmt->execute(argLists[0]->examine());
				return new Boolean(res);
			}
			catch (sql::SQLException& e) {
				cout << "# ERR: SQLException in " << __FILE__;
				cout << "(" << __FUNCTION__ << ") on line "
					<< __LINE__ << endl;
				cout << "# ERR: " << e.what();
				cout << " (MySQL error code: " << e.getErrorCode();
				cout << ", SQLState: " << e.getSQLState() << " )" << endl;
				return new Boolean(false);
			}
		}

		Object* fetch_data(std::vector<Object*> argLists) {
			std::map<std::string, keyVal*> data;
			size_t row_count = 1;
			while (_res->next()) {
				auto columnCount = _res->getMetaData()->getColumnCount();
				std::map<std::string, keyVal*> columnData;
				for (int i = 1; i <= columnCount; ++i) {
					StringObject* key = new StringObject(_res->getMetaData()->getColumnLabel(i));
					StringObject* val = new StringObject(_res->getString(key->_value));
					columnData.insert(std::make_pair(key->_value, new keyVal(key, val)));
				}
				StringObject* row_index = new StringObject(std::to_string(row_count));
				data[row_index->_value] = new keyVal(row_index, new ArrayObject(columnData));
				row_count++;
			}
			return new ArrayObject(data);
		}

		Object* close(std::vector<Object*> argLists) {
			if (_con->isClosed()) {
				return new Error("MysqlServer->close error, connection has already closed");
			}
			_con->close();
			delete _res;
			delete _con;
			return nullptr;
		}

	private:
		sql::Driver* _driver;
		sql::Connection* _con;
		sql::ResultSet* _res;
	};

}

#endif // !_TCPSERVER_HPP